package logica;

import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import logica.persistencia.JerarquiaPersistente;
import memento.IMemento;
import memento.MementoJerarquiaLogica;

public class Jerarquia extends ObjetoDiagrama {
	
	private Entidad entidadPadre;
	private List<Entidad> entidadesEspecializadas;
	private boolean esParticionCompleta;
	private boolean haySolapamiento;
	
	public Jerarquia(String nombre, Entidad entidadPadre, List<Entidad> entidadesEspecialadas) throws JerarquiaException, SinNombreException
	{
		this(nombre, entidadPadre, entidadesEspecialadas, true, false);
	}
	
	public Jerarquia(String nombre, Entidad entidadPadre, List<Entidad> entidadesEspecialadas, boolean esParticionCompleta, boolean haySolapamiento) throws JerarquiaException, SinNombreException
	{
		super(nombre);
		
		this.entidadPadre = entidadPadre;
		this.entidadesEspecializadas = entidadesEspecialadas;
		this.haySolapamiento = haySolapamiento;
		this.esParticionCompleta = esParticionCompleta;
		
		validarJerarquia();
	}
	
	public void setEntidadEspecilizada(List<Entidad> entidadesEspecializadas) throws JerarquiaException
	{
		this.entidadesEspecializadas = entidadesEspecializadas;
		this.validarJerarquia();
	}
	
	public Entidad getEntidadPadre() {
		return this.entidadPadre;
	}
	
	public List<Entidad> getEntidadesEspecializadas() {
		return this.entidadesEspecializadas;
	}

	public void setEsParticionCompleta(boolean esParticionCompleta) {
		this.esParticionCompleta = esParticionCompleta;
		this.setChanged();
		this.notifyObservers();
	}

	public boolean esParticionCompleta() {
		return esParticionCompleta;
	}

	public void setHaySolapamiento(boolean haySolapamiento) {
		this.haySolapamiento = haySolapamiento;
		this.setChanged();
		this.notifyObservers();
	}

	public boolean haySolapamiento() {
		return haySolapamiento;
	}

	public String getEsParticionCompleta()
	{
		if (esParticionCompleta)
			return "T"; //de total
		return "P"; //de parcial
	}

	public String getHaySolapamiento() 
	{
		if (haySolapamiento)
			return "NE"; //de no exclusica, en los ejemplos no hay letras para estoexclusiva
		return "E"; // de exclusica
	}
	
	private void validarJerarquia() throws JerarquiaException 
	{
		verificarSiTienePadre();
		verificarSiTieneHijos();
		verificarSiPadreEsHijo();		
		verificarSiHijosRepetidos();
		verificarSiAtributosDeHijoEstanEnPadre();
	}

	private void verificarSiTieneHijos() throws JerarquiaSinHijosException 
	{
		if (this.entidadesEspecializadas.size() == 0)
			throw new JerarquiaSinHijosException("No se especificaron entidades especializadas en la jerarquia.");
	}

	private void verificarSiTienePadre() throws JerarquiaSinPadreException
	{
		if (this.entidadPadre == null)
			throw new JerarquiaSinPadreException("No se especifico una entidad general en la jerarquia.");
	}

	private void verificarSiHijosRepetidos() throws JerarquiaException 
	{
		/*no se si se puede hacer de una manera mas sencilla (1:02)*/
        Collections.sort(this.entidadesEspecializadas, new Comparator<Entidad>() {  
  
            public int compare(Entidad o1, Entidad o2) { 
              
                return ((Entidad)o1).compareTo(o2);  
            }
        });  
  
        Iterator<Entidad> iterador = this.entidadesEspecializadas.iterator();
        Entidad hija1 = null;
        
        if (iterador.hasNext())
        	hija1 = iterador.next();
        
		while(iterador.hasNext())
		{
			Entidad hija2 = iterador.next();
			
			if(hija1.equals(hija2))
				throw new JerarquiaException("Una entidad no puede tener entidad especializada repetidas. Ver: " + hija2);
			
			hija1 = hija2;
		}
	}

	private void verificarSiPadreEsHijo() throws JerarquiaException 
	{
		for(Entidad hija: this.entidadesEspecializadas)
		{
			if(this.entidadPadre.equals(hija))
				throw new JerarquiaException("Una entidad no puede tenerse como entidad especializada asi misma. Ver: " + hija);
		}
	}


	private void verificarSiAtributosDeHijoEstanEnPadre() throws JerarquiaException 
	{
		for(Entidad hija: this.entidadesEspecializadas)
		{
			if(this.entidadPadre.equals(hija))
				throw new JerarquiaException("Una entidad no puede tenerse como entidad especializada asi misma. Ver: " + hija);
		}
		
	}
	
	@Override
	public List<Entidad> getReferenciaAEntidadesExternas() 
	{
		List<Entidad> entidades = new LinkedList<Entidad>(this.entidadesEspecializadas);
		
		entidades.add(this.entidadPadre);
		
		return entidades;
	}


	public JerarquiaPersistente getObjetoDiagramaPersistente() {
		return new JerarquiaPersistente(this);
	}
	
	@Override
	public boolean equals(Object objeto)
	{
		if (objeto.getClass() != this.getClass())
			return false;
		
		Jerarquia jerarquia = (Jerarquia) objeto;
		
		if (!this.getEntidadPadre().equals(jerarquia.getEntidadPadre()))
			return false;
		
		for(Entidad hijaActual: jerarquia.getEntidadesEspecializadas())
		{
			if (!buscarSiSeEncuentraHijo(hijaActual))
				return false;
		}
		
		return true;
	}

	private boolean buscarSiSeEncuentraHijo(Entidad hijaABuscar)
	{
		for(Entidad hija: this.getEntidadesEspecializadas())
		{
			if(hijaABuscar.equals(hija))
				return true;
		}
		
		return false;
	}

	@Override
	public boolean tieneEstaEntidad(Entidad entidad)
	{
		if (this.getEntidadPadre() == entidad)
			return true;
		
		return this.buscarSiSeEncuentraHijo(entidad);
	}

	@Override
	public IMemento getMementoEspecializado() {
		return new MementoJerarquiaLogica(this);
	}

	@Override
	public void saveMemento(IMemento memento) {
		memento.cargarMemento();		
	}

	public void setEntidadPadre(Entidad entidadPadre) {
		this.entidadPadre = entidadPadre;
		
	}

}
